package gridmapper.server.planner;

import gridmapper.RobotHeading;

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

public class GridPlanner {
	public int gridX = 4;
	public int gridY = 6;
	public CellState[][] cells;
	public int Robot1_Direction;
	public Point Robot1_Location = new Point();
	public List<Point> moves; 
	public List<Point> openCells; //list of all open cells. easier to loop through than the entire grid
	public boolean travelling = false; 	//this is used when the robot is 
										//following a path generated by dyjkstra's algorithm
	
	
	public GridPlanner(int x, int y, Point robot1_start, int robot1_heading)
	{
		Robot1_Direction = robot1_heading;
		Robot1_Location = robot1_start;
	
		gridX = x;
		gridY = y;
		cells = new CellState[gridY][gridX];
		openCells = new ArrayList<Point>();
		initCells();
		
	}
	public void initCells()
	{
		for (int i = 0; i < gridY; i++) 
		{
			for (int j = 0; j < gridX; j++) 
			{
				cells[i][j] = CellState.open; // initialize all of the spots on
				// the grid
				openCells.add(new Point(j,i));
			}
		}
		
	}
	public Point readNextMove(boolean deleteFromList)
	{
		if (moves == null)
		{
			travelling = false;
			return null;
		}
		Point move = (Point) moves.get(0).clone();
		if (deleteFromList)
			moves.remove(0);
		
		if (moves.size() == 0)
			travelling = false;
		
		Robot1_Direction = convertCoordinateToHeading(Robot1_Location, move);
		Robot1_Location = move;
		changeCellState(Robot1_Location, CellState.explored, false);
		markScannedCells(Robot1_Location.x, Robot1_Location.y, Robot1_Direction);
		
		return move;
	}
	public boolean containsOccupiedCell(List<Point> m)
	{
		for (Point move : m)
		{
			if (cells[move.y][move.x] == CellState.scannedOccupied)
				return true;
		}
		
		return false;
	}
	public void searchMoveCountBased()
	{
		int leastMoves = 1000;
		GridGrapher graph;
		for (Point cell : openCells)
		{
			graph = new GridGrapher(this, Robot1_Location, cell);
			try
			{
				graph.run();
			}
			catch (Exception e)
			{
				System.out.println(e);
			}
			if (graph.moves != null && graph.moves.size() != 0 && !containsOccupiedCell(graph.moves))
			{
				if (graph.moves.size() < leastMoves){
					leastMoves = graph.moves.size();
					moves = graph.moves;
				}
			}
		}

	}
	public void searchDistanceBased()
	{
		
		Point bestPt = null;
		boolean done = false;
		List<Point> usedCells = new ArrayList<Point>(); //maintains a list of cells we've tried to map

		while(!done)
		{
			int bestDistance = 100;

			for (Point cell : openCells)
			{
				if (!usedCells.contains(cell))
				{
					//calculate the distance from the current cell
					int dist = Math.abs(Robot1_Location.x - cell.x) + Math.abs(Robot1_Location.y - cell.y);
					
					if (dist < bestDistance) //if the distance is better than our current best
					{
						bestDistance = dist;
						bestPt = new Point(cell.x, cell.y);
					}
				}
			}
			usedCells.add(bestPt);

			//if we've run out of cells to check or there is no path found
			if (usedCells.size() == openCells.size() || bestPt == null)
				done = true; 
			
			GridGrapher graph = new GridGrapher(this, Robot1_Location, bestPt);
			try
			{
				graph.run(); //try to create a graph
			}
			catch (Exception e)
			{
				System.out.println(e);
			}
			if (graph.moves != null && graph.moves.size() != 0) //if we have a valid path, use it~!
			{
				done = true;
				moves = graph.moves;
			}
		}

	}
	public void run()
	{
		
		if (!areCellsOpen())
		{
			moves = null; //no need to look for moves if the grid has already been scanned fully
			return;
		}
		travelling = true;
		moves = null;
		//searchDistanceBased(); //find closest cell to the robot based on the calculated distance
		searchMoveCountBased(); //find closest cell to the robot based on the number of moves
		
		
	}
	public boolean hasOpenCells(List<Point> moves)
	{
		for (Point move : moves)
		{
			if (cells[move.y][move.x] == CellState.open)
				return true;
		}
		
		return false;
	}
	public boolean areCellsOpen()
	{
		for (int i = 0; i < gridX; i++)
		{
			for (int j = 0; j < gridY; j++)
			{
				if (cells[j][i] == CellState.open)
					return true;
				
			}	
		}
		return false;
	}
	public int getRotatedDirection(int direction, boolean isLeft)
	{
		//gets the value if the robot is rotated left or right based on its current direction
		int factor = 90;
		if (isLeft)
			factor *= -1;
		direction += factor;
		if (direction == 0)
			direction = 360;
		if (direction == 450)
			direction = 90;
		return direction;
		
	}
	public List<Point> getRowMoves(Point startPt, int direction)
	{
		int x = 0, y = 0;
		
		switch (direction)
		{
			case RobotHeading.NORTH:
				y++;
				break;
			case RobotHeading.SOUTH:
				y--;
				break;
			case RobotHeading.EAST:
				x--;
				break;
			case RobotHeading.WEST:
				x++;
				break;
		}
		List<Point> moves = new ArrayList<Point>();
		Point move = new Point(startPt.x + x, startPt.y + y); //move in whatever direction
		while (isCellValid(move.x, move.y))
		{
			moves.add((Point) move.clone());
			
			move.x += x;
			move.y += y;
		}
		return moves;
	}
	public void markScannedCells(int X, int Y, int direction) {
		// if heading north, we can only scan our North,East,West cells

		if (isCellInRange(X, Y)) // if the cell given is within range
		{
			if (isCellInRange(X + 1, Y)
					&& direction != gridmapper.RobotHeading.EAST) // will be
			// the WEST
			// cell
			{
				changeCellState(X + 1, Y, CellState.scannedOpen, false);
			}

			if (isCellInRange(X - 1, Y)
					&& direction != gridmapper.RobotHeading.WEST) // will be
			// the EAST
			// cell
			{
				changeCellState(X - 1, Y, CellState.scannedOpen, false);
			}

			if (isCellInRange(X, Y + 1)
					&& direction != gridmapper.RobotHeading.SOUTH) // will be
			// the NORTH
			// cell
			{
				changeCellState(X, Y + 1, CellState.scannedOpen, false);
			}

			if (isCellInRange(X, Y - 1)
					&& direction != gridmapper.RobotHeading.NORTH) // will be
			// the SOUTH
			// cell
			{
				changeCellState(X, Y - 1, CellState.scannedOpen, false);
			}
		}
	}
	public int convertCoordinateToHeading(Point currentLocation, Point newMove) {
		return convertCoordinateToHeading(currentLocation.x, currentLocation.y,
				newMove.x, newMove.y);
	}

	// based on the robots current position and the position it will be going
	// to, we can determine the robots heading
	public int convertCoordinateToHeading(int currentX, int currentY, int newX,
			int newY) {
		int dir = 0;
		if (newX < currentX) // moved east
			dir = gridmapper.RobotHeading.EAST;
		else if (newX > currentX) // moved east
			dir = gridmapper.RobotHeading.WEST;
		else if (newY > currentY) // moved north
			dir = gridmapper.RobotHeading.NORTH;
		else if (newY < currentY) // moved south
			dir = gridmapper.RobotHeading.SOUTH;
		else
			dir = RobotHeading.WAIT;

		return dir;
	}
	public Point convertHeadingToCoordinate(Point currentLocation, int heading) {
		Point retVal = new Point(currentLocation);

		switch (heading) {
		case RobotHeading.NORTH:
			retVal.translate(0, 1);
			break;
		case RobotHeading.EAST:
			retVal.translate(-1, 0);
			break;
		case RobotHeading.SOUTH:
			retVal.translate(0, -1);
			break;
		case RobotHeading.WEST:
			retVal.translate(1, 0);
			break;
		case RobotHeading.WAIT:
			break;
		default:
			throw new IllegalArgumentException("heading=" + heading
					+ " is invalid.");
		}

		return retVal;
	}
	
	public void changeCellState(Point location, CellState newState, boolean overwriteOccupiedCells) {
		if (location == null || !isCellInRange(location.x, location.y))
			return;
		changeCellState(location.x, location.y, newState, overwriteOccupiedCells);
	}

	public void changeCellState(int x, int y, CellState newState, boolean overwriteOccupiedCells) {
		if (!overwriteOccupiedCells && cells[y][x] == CellState.scannedOccupied)
			return;
		 if (newState != CellState.open)
			 openCells.remove(new Point(x,y));
		 else if (newState == CellState.open)
			 openCells.add(new Point(x,y));
		 
		 cells[y][x] = newState;

	}
	public int getScoreForMoves(List<Point> moves)
	{
		if (moves == null || moves.size() == 0)
			return -999;
		int score = 0;
		
		for (Point move : moves)
		{
			if (cells[move.y][move.x] == CellState.open)
				score += 10;
			if (cells[move.y][move.x] == CellState.scannedOpen)
				score -= 5;
		}
		
		return score;
	}
	private boolean isCellInRange(int X, int Y) {
		if (X >= gridX || Y >= gridY) // if the grid cell is out of range.
			return false;
		if (X < 0 || Y < 0) // if the grid cell is out of range.
			return false;

		return true;
	}

	public boolean isCellValid(int X, int Y) {
		if (!isCellInRange(X, Y))
			return false;
		CellState cell = cells[Y][X];
		if (cell == CellState.scannedOccupied)// || cell == CellState.scannedOpen)
			return false;

		return true;
	}
}
